
/**
 * \file etc.c
 * \author linyun <linyun@ysten.com>
 * \date 2011/12/14
 * 
 * This file includes etc_fns Configuration file operations.
 *
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>

#include "etc.h"

#define NR_KEYS_INIT_ALLOC      8
#define NR_KEYS_INC_ALLOC       4

#define NR_SECTS_INIT_ALLOC     16
#define NR_SECTS_INC_ALLOC      8

/****************************** ETC file support ******************************/
static char *get_section_name (char *section_line)
{
	char *current;
	char *name;

	if (!section_line)
		return NULL;

	current = section_line;

	while (*current == ' ' || *current == '\t')
		current++;

	if (*current == ';' || *current == '#')
		return NULL;

	if (*current++ == '[')
		while (*current == ' ' || *current == '\t')
			current++;
	else
		return NULL;

	name = current;
	while (*current != ']' && *current != '\n' && *current != ';' && *current != '#' && *current != '\0')
		current++;
	*current = '\0';
	while (*current == ' ' || *current == '\t')
	{
		*current = '\0';
		current--;
	}

	return name;
}

static int get_key_value (char *key_line, char **mykey, char **myvalue)
{
	char *current;
	char *tail;
	char *value;

	if (!key_line)
		return -1;

	current = key_line;

	while (*current == ' ' || *current == '\t')
		current++;

	if (*current == ';' || *current == '#')
		return -1;

	if (*current == '[')
		return 1;

	if (*current == '\n' || *current == '\0')
		return -1;

	tail = current;
	while (*tail != '=' && *tail != '\n' && *tail != ';' && *tail != '#' && *tail != '\0')
		tail++;

	value = tail + 1;
	if (*tail != '=')
		*value = '\0';

	*tail-- = '\0';
	while (*tail == ' ' || *tail == '\t')
	{
		*tail = '\0';
		tail--;
	}

	tail = value;
	while (*tail != '\n' && *tail != '\0')
		tail++;
	*tail = '\0';

	if (mykey)
		*mykey = current;
	if (myvalue)
		*myvalue = value;

	return 0;
}


/* This function locate the specified section in the etc file. */
static int etc_LocateSection (FILE * fp, const char *pSection, FILE * bak_fp)
{
	char szBuff[ETC_MAXLINE + 1];
	char *name;

	while (TRUE)
	{
		if (!fgets (szBuff, ETC_MAXLINE, fp))
		{
			if (feof (fp))
				return ETC_SECTIONNOTFOUND;
			else
				return ETC_FILEIOFAILED;
		}
		else if (bak_fp && fputs (szBuff, bak_fp) == EOF)
			return ETC_FILEIOFAILED;

		name = get_section_name (szBuff);
		if (!name)
			continue;

		if (strcmp (name, pSection) == 0)
			return ETC_OK;
	}

	return ETC_SECTIONNOTFOUND;
}

/* This function locate the specified key in the etc file. */
static int etc_LocateKeyValue (FILE * fp, const char *pKey, BOOL bCurSection, char *pValue, int iLen, FILE * bak_fp, char *nextSection)
{
	char szBuff[ETC_MAXLINE + 1 + 1];
	char *current;
	char *value;
	int ret;

	while (TRUE)
	{
		int bufflen;

		if (!fgets (szBuff, ETC_MAXLINE, fp))
			return ETC_FILEIOFAILED;
		bufflen = strlen (szBuff);
		if (szBuff[bufflen - 1] == '\n')
			szBuff[bufflen - 1] = '\0';

		ret = get_key_value (szBuff, &current, &value);
		if (ret < 0)
			continue;
		else if (ret > 0)
		{
			fseek (fp, -bufflen, SEEK_CUR);
			return ETC_KEYNOTFOUND;
		}

		if (strcmp (current, pKey) == 0)
		{
			if (pValue)
			{
				strncpy (pValue, value, iLen - 1);
				pValue[iLen - 1] = '\0';
			}

			return ETC_OK;
		}
		else if (bak_fp && *current != '\0')
		{
#if defined (__THREADX__) && defined (__TARGET_VFANVIL__)
//FIXME, unknown problem with fprintf
			char tmp_nam[256];

			sprintf (tmp_nam, "%s=%s\n", pKey, pValue);
			fputs (tmp_nam, bak_fp);
#else
			fprintf (bak_fp, "%s=%s\n", current, value);
#endif
		}
	}

	return ETC_KEYNOTFOUND;
}

static PETCSECTION etc_NewSection (ETC_S * petc)
{
	PETCSECTION psect;

	if (petc->section_nr == petc->sect_nr_alloc)
	{
		/* add 5 sections each time we realloc */
		petc->sect_nr_alloc += NR_SECTS_INC_ALLOC;
		petc->sections = realloc (petc->sections, sizeof (ETCSECTION) * petc->sect_nr_alloc);
	}
	psect = petc->sections + petc->section_nr;

	psect->name = NULL;

	petc->section_nr++;

	return psect;
}

static void etc_NewKeyValue (PETCSECTION psect, const char *key, const char *value)
{
	if (psect->key_nr == psect->key_nr_alloc)
	{
		psect->key_nr_alloc += NR_KEYS_INC_ALLOC;
		psect->keys = realloc (psect->keys, sizeof (char *) * psect->key_nr_alloc);
		psect->values = realloc (psect->values, sizeof (char *) * psect->key_nr_alloc);
	}

	psect->keys[psect->key_nr] = strdup (key);
	psect->values[psect->key_nr] = strdup (value);
	psect->key_nr++;
}

static int etc_ReadSection (FILE * fp, PETCSECTION psect)
{
	char szBuff[ETC_MAXLINE + 1 + 1];
	char *sect_name;

	psect->name = NULL;
	psect->key_nr = 0;
	psect->keys = NULL;
	psect->values = NULL;

	while (TRUE)
	{
		int bufflen;

		if (!fgets (szBuff, ETC_MAXLINE, fp))
		{
			if (feof (fp))
			{
				if (psect->name)
					break;
				else
					return ETC_SECTIONNOTFOUND;
			}
			else
				return ETC_FILEIOFAILED;
		}

		bufflen = strlen (szBuff);
		if (szBuff[bufflen - 1] == '\n')
			szBuff[bufflen - 1] = '\0';

		if (!psect->name)
		{						/* read section name */
			sect_name = get_section_name (szBuff);
			if (!sect_name)
				continue;

			psect->name = strdup (sect_name);
			psect->key_nr = 0;
			psect->key_nr_alloc = NR_KEYS_INIT_ALLOC;
			psect->keys = malloc (sizeof (char *) * NR_KEYS_INIT_ALLOC);
			psect->values = malloc (sizeof (char *) * NR_KEYS_INIT_ALLOC);
		}
		else
		{						/* read key and value */
			int ret;
			char *key, *value;

			ret = get_key_value (szBuff, &key, &value);
			if (ret < 0)
				continue;
			else if (ret > 0)
			{					/* another section begins */
				fseek (fp, -bufflen, SEEK_CUR);
				break;
			}

			etc_NewKeyValue (psect, key, value);
		}
	}

	return ETC_OK;
}

static int etc_WriteSection (FILE * fp, PETCSECTION psect, BOOL bSectName)
{
	int i;

	if (psect->name == NULL)
		return ETC_OK;

	if (bSectName)
		fprintf (fp, "[%s]\n", psect->name);

	for (i = 0; i < psect->key_nr; i++)
	{
		fprintf (fp, "%s=%s\n", psect->keys[i], psect->values[i]);
	}

	if (fprintf (fp, "\n") != 1)
		return ETC_FILEIOFAILED;

	return ETC_OK;
}

static int etc_GetSectionValue (PETCSECTION psect, const char *pKey, char *pValue, int iLen)
{
	int i;

	for (i = 0; i < psect->key_nr; i++)
	{
		if (strcmp (psect->keys[i], pKey) == 0)
		{
			break;
		}
	}

	if (iLen > 0)
	{							/* get value */
		if (i >= psect->key_nr)
			return ETC_KEYNOTFOUND;

		strncpy (pValue, psect->values[i], iLen - 1);
		pValue[iLen - 1] = '\0';
	}
	else
	{							/* set value */
		if (psect->key_nr_alloc <= 0)
			return ETC_READONLYOBJ;

		if (i >= psect->key_nr)
		{
			etc_NewKeyValue (psect, pKey, pValue);
		}
		else
		{
			free (psect->values[i]);
			psect->values[i] = strdup (pValue);
		}
	}

	return ETC_OK;
}

GHANDLE LoadEtcFile (const char *pEtcFile)
{
	FILE *fp = NULL;
	ETC_S *petc;

	if (pEtcFile && !(fp = fopen (pEtcFile, "r")))
		return 0;

	petc = (ETC_S *) malloc (sizeof (ETC_S));
	petc->section_nr = 0;
	petc->sect_nr_alloc = NR_SECTS_INIT_ALLOC;

	/* we allocate 15 sections first */
	petc->sections = (PETCSECTION) malloc (sizeof (ETCSECTION) * NR_SECTS_INIT_ALLOC);

	if (pEtcFile == NULL)
	{							/* return an empty etc object */
		return (GHANDLE) petc;
	}

	while (etc_ReadSection (fp, petc->sections + petc->section_nr) == ETC_OK)
	{
		petc->section_nr++;
		if (petc->section_nr == petc->sect_nr_alloc)
		{
			/* add 5 sections each time we realloc */
			petc->sect_nr_alloc += NR_SECTS_INC_ALLOC;
			petc->sections = realloc (petc->sections, sizeof (ETCSECTION) * petc->sect_nr_alloc);
		}
	}

	fclose (fp);
	return (GHANDLE) petc;
}

int UnloadEtcFile (GHANDLE hEtc)
{
	int i;
	ETC_S *petc = (ETC_S *) hEtc;

	if (!petc)
		return -1;

	for (i = 0; i < petc->section_nr; i++)
	{
		PETCSECTION psect = petc->sections + i;
		int j;

		if (!psect->name)
			continue;

		for (j = 0; j < psect->key_nr; j++)
		{
			free (psect->keys[j]);
			free (psect->values[j]);
		}
		free (psect->keys);
		free (psect->values);
		free (psect->name);
	}

	free (petc->sections);
	free (petc);

	return 0;
}


int GetValueFromEtc (GHANDLE hEtc, const char *pSection, const char *pKey, char *pValue, int iLen)
{
	int i, empty_section = -1;
	ETC_S *petc = (ETC_S *) hEtc;
	PETCSECTION psect = NULL;

	if (!petc || !pValue)
		return -1;

	for (i = 0; i < petc->section_nr; i++)
	{
		psect = petc->sections + i;
		if (!psect->name)
		{
			empty_section = i;
			continue;
		}

		if (strcmp (psect->name, pSection) == 0)
		{
			break;
		}
	}

	if (i >= petc->section_nr)
	{
		if (iLen > 0)
			return ETC_SECTIONNOTFOUND;
		else
		{
			if (petc->sect_nr_alloc <= 0)
				return ETC_READONLYOBJ;

			if (empty_section >= 0)
				psect = petc->sections + empty_section;
			else
			{
				psect = etc_NewSection (petc);
			}

			if (psect->name == NULL)
			{
				psect->key_nr = 0;
				psect->name = strdup (pSection);
				psect->key_nr_alloc = NR_KEYS_INIT_ALLOC;
				psect->keys = malloc (sizeof (char *) * NR_KEYS_INIT_ALLOC);
				psect->values = malloc (sizeof (char *) * NR_KEYS_INIT_ALLOC);
			}
		}
	}

	return etc_GetSectionValue (psect, pKey, pValue, iLen);
}

int GetIntValueFromEtc (GHANDLE hEtc, const char *pSection, const char *pKey, int *value)
{
	int ret;
	char szBuff[51];

	ret = GetValueFromEtc (hEtc, pSection, pKey, szBuff, 50);
	if (ret < 0)
	{
		return ret;
	}

	*value = strtol (szBuff, NULL, 0);
	if ((*value == LONG_MIN || *value == LONG_MAX) && errno == ERANGE)
		return ETC_INTCONV;

	return ETC_OK;
}

/**
 * \fn int  SetIntValueToEtc (GHANDLE hEtc, const char* pSection, const char* pKey, int nValue)
 * \brief Sets the integer value to a configuration etc object.
 *
 * \sa SetValueToEtc
 */
int SetIntValueToEtc (GHANDLE hEtc, const char *pSection, const char *pKey, int nValue)
{
	char szValue[16];

	snprintf (szValue, sizeof (szValue) - 1, "%d", nValue);
	szValue[sizeof (szValue) - 1] = '\0';

	return SetValueToEtc (hEtc, pSection, pKey, szValue);
}

/* Function: GetValueFromEtcFile(const char* pEtcFile, const char* pSection,
 *                               const char* pKey, char* pValue, int iLen);
 * Parameter:
 *     pEtcFile: etc file path name.
 *     pSection: Section name.
 *     pKey:     Key name.
 *     pValue:   The buffer will store the value of the key.
 *     iLen:     The max length of value string.
 * Return:
 *     int               meaning
 *     ETC_FILENOTFOUND           The etc file not found. 
 *     ETC_SECTIONNOTFOUND        The section is not found. 
 *     ETC_EKYNOTFOUND        The Key is not found.
 *     ETC_OK            OK.
 */
int GetValueFromEtcFile (const char *pEtcFile, const char *pSection, const char *pKey, char *pValue, int iLen)
{
	FILE *fp;
	char tempSection[ETC_MAXLINE + 2];

	if (!(fp = fopen (pEtcFile, "r")))
		return ETC_FILENOTFOUND;

	if (pSection)
		if (etc_LocateSection (fp, pSection, NULL) != ETC_OK)
		{
			fclose (fp);
			return ETC_SECTIONNOTFOUND;
		}

	if (etc_LocateKeyValue (fp, pKey, pSection != NULL, pValue, iLen, NULL, tempSection) != ETC_OK)
	{
		fclose (fp);
		return ETC_KEYNOTFOUND;
	}

	fclose (fp);
	return ETC_OK;
}

/* Function: GetIntValueFromEtcFile(const char* pEtcFile, const char* pSection,
 *                               const char* pKey);
 * Parameter:
 *     pEtcFile: etc file path name.
 *     pSection: Section name.
 *     pKey:     Key name.
 * Return:
 *     int                      meaning
 *     ETC_FILENOTFOUND             The etc file not found. 
 *     ETC_SECTIONNOTFOUND          The section is not found. 
 *     ETC_EKYNOTFOUND              The Key is not found.
 *     ETC_OK                       OK.
 */
int GetIntValueFromEtcFile (const char *pEtcFile, const char *pSection, const char *pKey, int *value)
{
	int ret;
	char szBuff[51];

	ret = GetValueFromEtcFile (pEtcFile, pSection, pKey, szBuff, 50);
	if (ret < 0)
		return ret;

	*value = strtol (szBuff, NULL, 0);
	if ((*value == LONG_MIN || *value == LONG_MAX) && errno == ERANGE)
		return ETC_INTCONV;

	return ETC_OK;
}

int SaveEtcToFile (GHANDLE hEtc, const char *file_name)
{
	int i;
	FILE *fp;
	ETC_S *petc = (ETC_S *) hEtc;

	if (petc == NULL)
		return ETC_INVALIDOBJ;

	if (!(fp = fopen (file_name, "w")))
	{
		return ETC_FILEIOFAILED;
	}

	for (i = 0; i < petc->section_nr; i++)
	{
		if (etc_WriteSection (fp, petc->sections + i, TRUE))
		{
			fclose (fp);
			return ETC_FILEIOFAILED;
		}
	}

	fclose (fp);
	return ETC_OK;
}
